// 导入必要的模块
import express from "express";
import cors from "cors";
import multer from "multer";
import path from "path";
import fs from "fs-extra";
import { fileURLToPath } from "url";

// 获取当前文件的目录路径（ES6模块中需要手动设置__dirname）
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// 创建Express应用实例
const app = express();
const PORT = 3000;

// 定义文件存储目录
const UPLOAD_DIR = path.resolve(__dirname, "uploads"); // 最终上传文件的存储目录
const CHUNK_DIR = path.resolve(__dirname, "chunks"); // 临时切片文件的存储目录

// 确保存储目录存在，如果不存在则创建
fs.ensureDirSync(UPLOAD_DIR);
fs.ensureDirSync(CHUNK_DIR);

// 配置中间件
app.use(cors()); // 启用跨域资源共享
app.use(express.json({ limit: "50mb" })); // 解析JSON请求体，限制大小为50MB
app.use(express.urlencoded({ extended: true, limit: "50mb" })); // 解析URL编码的请求体

// 配置multer用于处理文件上传
const upload = multer({ dest: CHUNK_DIR }); // 设置临时文件存储目录

/**
 * 合并文件切片的辅助函数
 * @param {string} fileHash - 文件的哈希值
 * @param {string} filename - 原始文件名
 */
const mergeChunks = async (fileHash, filename) => {
    // 获取切片文件所在的目录
    const chunkDir = path.resolve(CHUNK_DIR, fileHash);
    // 读取目录中的所有切片文件
    const chunkPaths = await fs.readdir(chunkDir);

    // 根据切片索引排序，确保按正确顺序合并
    chunkPaths.sort((a, b) => a.split("-")[1] - b.split("-")[1]);

    // 设置最终合并后的文件路径
    const filePath = path.resolve(UPLOAD_DIR, filename);

    // 确保目标文件存在
    await fs.ensureFile(filePath);

    // 将所有切片合并到最终文件
    await Promise.all(
        chunkPaths.map((chunkPath, index) => {
            const sourcePath = path.resolve(chunkDir, chunkPath);
            const start = index * (2 * 1024 * 1024); // 计算每个切片在最终文件中的起始位置（假设切片大小为2MB）
            const stream = fs.createReadStream(sourcePath);
            const writeStream = fs.createWriteStream(filePath, { start });

            return new Promise((resolve, reject) => {
                // 将切片内容写入最终文件
                stream.pipe(writeStream);
                stream.on("end", async () => {
                    // 删除已合并的切片文件
                    await fs.unlink(sourcePath);
                    resolve();
                });
                stream.on("error", reject);
            });
        })
    );

    // 删除临时切片目录
    await fs.rmdir(chunkDir);
};

/**
 * 检查文件状态接口（用于秒传和断点续传）
 * @api {post} /upload/check
 * @apiParam {String} fileHash 文件内容的Hash值
 * @apiParam {String} filename 文件名
 */
app.post("/upload/check", async (req, res) => {
    const { fileHash, filename } = req.body;

    // 验证必要参数
    if (!fileHash || !filename) {
        return res.status(400).json({ success: false, message: "参数缺失" });
    }

    const filePath = path.resolve(UPLOAD_DIR, filename);

    try {
        // 检查文件是否已完整上传（秒传检查）
        if (fs.existsSync(filePath)) {
            return res.json({
                success: true,
                uploaded: true,
                message: "文件已存在，实现秒传",
            });
        }

        // 检查是否存在部分上传的切片（断点续传检查）
        const chunkDir = path.resolve(CHUNK_DIR, fileHash);
        if (fs.existsSync(chunkDir)) {
            const uploadedChunks = await fs.readdir(chunkDir);
            return res.json({
                success: true,
                uploaded: false,
                uploadedChunks: uploadedChunks.map(
                    (name) => name.split("-")[1]
                ), // 返回已上传的切片索引
            });
        }

        // 文件不存在，也没有部分上传的切片
        res.json({
            success: true,
            uploaded: false,
            uploadedChunks: [],
        });
    } catch (error) {
        console.error("Check failed:", error);
        res.status(500).json({ success: false, message: "检查文件失败" });
    }
});

/**
 * 上传文件切片接口
 * @api {post} /upload/chunk
 * @apiParam {File} chunk 文件切片
 * @apiParam {String} fileHash 文件内容的Hash值
 * @apiParam {String} chunkIndex 切片索引
 */
app.post("/upload/chunk", upload.single("chunk"), async (req, res) => {
    try {
        const { fileHash, chunkIndex } = req.body;
        const chunkDir = path.resolve(CHUNK_DIR, fileHash);

        // 确保以hash命名的切片目录存在
        if (!fs.existsSync(chunkDir)) {
            await fs.mkdirs(chunkDir);
        }

        // multer会将文件保存在CHUNK_DIR，我们需要将其移动到以fileHash命名的子目录中
        const oldPath = req.file.path; // multer保存的临时文件路径
        const newPath = path.resolve(chunkDir, `${fileHash}-${chunkIndex}`); // 新的切片文件路径
        await fs.move(oldPath, newPath, { overwrite: true });

        res.json({ success: true, message: `切片 ${chunkIndex} 上传成功` });
    } catch (error) {
        console.error("Chunk upload failed:", error);
        res.status(500).json({ success: false, message: "切片上传失败" });
    }
});

/**
 * 合并文件切片接口
 * @api {post} /upload/merge
 * @apiParam {String} fileHash 文件内容的Hash值
 * @apiParam {String} filename 原始文件名
 * @apiParam {Number} chunkSize 切片大小
 */
app.post("/upload/merge", async (req, res) => {
    const { fileHash, filename, chunkSize } = req.body;

    try {
        // 调用合并函数
        await mergeChunks(fileHash, filename);
        res.json({ success: true, message: "文件合并成功" });
    } catch (error) {
        console.error("Merge failed:", error);
        res.status(500).json({ success: false, message: "文件合并失败" });
    }
});

// 启动服务器
app.listen(PORT, () => {
    console.log(`后端服务已启动，正在监听 http://localhost:${PORT}`);
});
